package persistence ;

import java.io.Serializable;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.compass.annotations.SearchableId;

/**
 *
 * in every oop application, every object has a unique identity - its memory address. objects that need to
 * persist must retain their identity between program runs, and therefore cannot rely on their memory address
 * as identity. therefore persistent objects need an alternative id system.
 *
 * a persistent class is such that retains its id across multiple program runs. id is currently long. lets
 * hope it remains so, because changing it is too much, and java does not offer typedefs :(
 *
 * the following class invariant is NOT guaranteed automatically by the class, but should be guaranteed by its
 * creator:
 * @inv p1.getId() == p2.getId() <==> p1 == p2
 * @author idan
 */
public abstract class Persistent implements Serializable
{
	/**
	 * signals an invalid id. primely for use with blank objects which should be filled by deserialization
	 * code.
	 */
	public static final long INVALID_ID = -1 ;

	private static final long serialVersionUID = -1795526515464707530L ;

	/**
	 * this is supposed to be in a different class, however, java doesnt have multiple inheritance, so it
	 * cannot be inherited from a different class.
	 */
	private transient ReadWriteLock lock ;

	@SearchableId
	private long id ;

	/**
	 * default is no id.
	 */
	protected Persistent()
	{
		this(Persistent.INVALID_ID) ;
	}

	/**
	 * @param id id of this object.
	 * @post this.getId() = id.
	 */
	protected Persistent(final long id)
	{
		super() ;
		this.id = id ;
		this.initializaDefaultMembers() ;
	}

	/**
	 * p1.equals(p2) iff p1.getId() = p2.getId().
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object obj)
	{
		if (this == obj)
		{
			return true ;
		}
		if (obj == null)
		{
			return false ;
		}
		if (this.getClass() != obj.getClass())
		{
			return false ;
		}
		final Persistent other = (Persistent)obj ;
		if (this.id != other.id)
		{
			return false ;
		}
		return true ;
	}

	/**
	 * @return unique id of this object.
	 */
	public long getId()
	{
		return this.id ;
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode()
	{
		final int prime = 31 ;
		int result = 1 ;
		result = prime * result + (int)(this.id ^ (this.id >>> 32)) ;
		return result ;
	}

	/**
	 * @param id id to set.
	 */
	public void setId(final long id)
	{
		this.id = id ;
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		return "[id: " + String.valueOf(this.getId()) + "]" ;
	}

	/**
	 * lock this object for reading.
	 */
	protected void lockRead()
	{
		this.lock.readLock().lock() ;
	}

	/**
	 * lock this object for writing.
	 */
	protected void lockWrite()
	{
		this.lock.writeLock().lock() ;
	}

	/**
	 * post-deserialization procedure.
	 * @return this object.
	 */
	protected Object readResolve()
	{
		this.initializaDefaultMembers() ;
		return this ;
	}

	/**
	 * unlock this object from reading.
	 */
	protected void unlockRead()
	{
		this.lock.readLock().unlock() ;
	}

	/**
	 * unlock this object from writing.
	 */
	protected void unlockWrite()
	{
		this.lock.writeLock().unlock() ;
	}

	private void initializaDefaultMembers()
	{
		this.lock = new ReentrantReadWriteLock() ;
	}
}
